package com.caits.lbs.framework.utils;

import java.util.LinkedList;
import java.util.List;

import org.slf4j.Logger;

import com.caits.lbs.framework.bean.common.GeoPoint;
import com.caits.lbs.framework.log.CommonSlf4jLogFactory;

/**
 * <p>GeoUtils</p>
 * <p>地理计算专用工具类</p>
 *
 * @author		周华彬(zhouhuabin@ctfo.com, zhou_hua_bin@163.com)
 * @version		0.0.0
 * <table style="border:1px solid gray;">
 * <tr>
 * <th width="100px">版本号</th><th width="100px">动作</th><th width="100px">修改人</th><th width="100px">修改时间</th>
 * </tr>
 * <!-- 以 Table 方式书写修改历史 -->
 * <tr>
 * <td>0.0.0</td><td>创建类</td><td>zhouhuabin</td><td>2014年9月25日 下午2:12:12</td>
 * </tr>
 * <tr>
 * <td>XXX</td><td>XXX</td><td>XXX</td><td>XXX</td>
 * </tr>
 * </table>
*/
public class GeoUtils {
	protected static Logger LOG = CommonSlf4jLogFactory.getLog();

	/**
	 * 计算两经纬度坐标间的大圆矩离 单位度
	 * @param pt1
	 *            位置1度
	 * @param pt2
	 *            位置2度
	 * @return long 两点间的距离(米)
	 */
	public static long getDistanceInGreatCircle(double lon1, double lat1, double lon2, double lat2) {
		// 将经纬度换算成度
		double x1, x2, y1, y2;
		x1 = lon1;
		y1 = lat1;
		x2 = lon2;
		y2 = lat2;
		double x = Math.cos(convertAngleToRadian(90 - y1)) * Math.cos(convertAngleToRadian(90 - y2)) + Math.sin(convertAngleToRadian(90 - y1)) * Math.sin(convertAngleToRadian(90 - y2))
				* Math.cos(convertAngleToRadian(x1 - x2));
		if (x > 0 && x > 1) {
			x = 0.99999;
		}
		if (x < 0 && x < -1) {
			x = -0.99999;
		}
		return (int) (getEqualVolumeEarthRadius() * Math.acos(x));
	}

	/**
	 * 角度到弧度转换
	 * 
	 * @param Agnel
	 *            角度
	 * @return double 弧度值
	 */
	public static double convertAngleToRadian(double Angle) {
		return Angle * 3.14159265358979 / 180.0;
	}

	/**
	 * 取得等体积球体半径
	 * 
	 * @return
	 */
	public static double getEqualVolumeEarthRadius() {
		double a = 6378245; // 克氏参数
		double b = 6356863;
		return Math.pow(a * a * b, 1.0 / 3.0);
	}

	/**
	 * 对点坐标进行加密处理--国家统一加密程序
	 * WGS84到gcj02坐标系
	 * @param x 单位度
	 * @param y 单位度
	 * @return  点对象，单位度
	 */
	public static GeoPoint encryPoint(double x, double y) {
		double x1, tempx;
		double y1, tempy;

		x1 = x * 3686400.0;
		y1 = y * 3686400.0;

		int gpsHeight = 0;
		int gpsWeek = 0;
		int gpsWeekTime = 0;

		double newx, newy;

		Converter converter = new Converter();

		GeoPoint point = converter.wgtochina_lb(1, (int) x1, (int) y1, gpsHeight, gpsWeek, gpsWeekTime);

		newx = point.getLatitude();
		newy = point.getLongitude();

		tempx = newx;
		tempy = newy;
		tempx = tempx / 3686400.0;
		tempy = tempy / 3686400.0;

		point = new GeoPoint();
		point.setLatitude(tempx);
		point.setLongitude(tempy);

		return point;
	}
	/**
	 * google编码折线算法 
	 * @param polyline	点数组,单位是度
	 * @return
	 */
	public static String encodeGooglePolyLine(GeoPoint[] polyline) {
		String output = "";
		int[] deltaNumbers = new int[2 * polyline.length];
		if (polyline.length > 0) {

			deltaNumbers[0] = (int) (Math
					.floor(polyline[0].getLatitude() * 100000));
			deltaNumbers[1] = (int) (Math
					.floor(polyline[0].getLongitude() * 100000));
			for (int i = 1; i < polyline.length; i++) {
				deltaNumbers[(2 * i)] = (int) ((Math.floor((polyline[i]
						.getLatitude() * 100000)
						- Math.floor(polyline[i - 1].getLatitude() * 100000))));
				deltaNumbers[(2 * i) + 1] = (int) ((Math.floor(polyline[i]
						.getLongitude() * 100000) - Math.floor(polyline[i - 1]
						.getLongitude() * 100000)));
			}
			output = encodeVectorSignedNumber(deltaNumbers).toString();
		}
		return output;
	}
	/**
	 * 计算一组点的编码增量 
	 * @param numbers
	 * @return String
	 */
	private static StringBuilder encodeVectorSignedNumber(int[] numbers) {
		StringBuilder output = new StringBuilder();
		for (int i = 0; i < numbers.length; i++) {
			numbers[i] <<= 1;
			if (numbers[i] < 0) {
				numbers[i] = ~(numbers[i]);
			}
		}
		for (int i = 0; i < numbers.length; i++) {
			output.append(encodeNumber(numbers[i]));
		}
		return output;
	}

	/**
	 * 单个整数的折线编码 
	 * @param numberToEncode
	 * @return StringBuilder
	 */
	private static StringBuilder encodeNumber(int numberToEncode) {
		StringBuilder output = new StringBuilder();
		while (numberToEncode >= 0x20) {
			int nextValue = (0x20 | (numberToEncode & 0x1f)) + 63;
			output.append((char) (nextValue));
			if (92 == nextValue)
				output.append((char) (nextValue));
			numberToEncode >>= 5;
		}
		numberToEncode += 63;
		output.append((char) (numberToEncode));
		//如果是转义符\则用\\表示
		if (92 == numberToEncode)
			output.append((char) (numberToEncode));
		return output;
	}
	/**
	 * google map 折线解码
	 * @param encoded 密文字符串
	 * @param points 坐标列表
	 */
	public static void decodeGooglePolyLine(String encoded, List<GeoPoint> points) {
		try {
			encoded = encoded.replaceAll("\\\\\\\\", "\\\\");
			// java中，\为转义字符，会自动为\多加一次变成\\
			// 比如_vcsFsxbyU~ExXzHt\\tqBjzL会变成_vcsFsxbyU~ExXzHt\\\\tqBjzL
			// List<LocationPoint> poly = new ArrayList<LocationPoint>();
			int index = 0, len = encoded.length();
			int lat = 0, lng = 0;
			points.clear();
			while (index < len) {
				int b, shift = 0, result = 0;
				do {
					b = encoded.charAt(index++) - 63;
					result |= (b & 0x1f) << shift;
					shift += 5;
				} while (b >= 0x20);
				int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
				lat += dlat;
				//
				shift = 0;
				result = 0;
				do {
					b = encoded.charAt(index++) - 63;
					result |= (b & 0x1f) << shift;
					shift += 5;
				} while (b >= 0x20);
				int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
				lng += dlng;
				points.add(new GeoPoint(lng/100000D,lat/100000D));
			}
		} catch (Exception e) {
			LOG.error("google折线编码异常,str="+encoded,e);
		}
		// return poly;
	}
	
	/**
	 * FIXME 
	 * @param args
	 */
	public static void main(String[] args) {
		String strSrc="可以加密汉字.Oh,and english";
		//高速点SEG_NO=100010,G2511
		//decoded=omoaG}xekVdfCcgBxeBecC~`LexKbb@{i@
		//        omoaG}xekVdfCcgBxeBecC~`LexKbb@{i@
		String strHighwayPoints = "122.5871963500977,42.35496520996094;122.6038513183594,42.33333206176758;122.625,42.3168830871582;122.6904754638672,42.25;122.6973342895508,42.24438095092773";
		String[] pointStrArray = strHighwayPoints.split(";");
		GeoPoint[] pointArray = new GeoPoint[pointStrArray.length];
		int count=0;
		for(String pointStr :pointStrArray){
			String[] tempArr = pointStr.split(",");
			pointArray[count++] = new GeoPoint(Double.parseDouble(tempArr[0]),Double.parseDouble(tempArr[1]));
		}
		long lstart = System.currentTimeMillis();
		count = 10;
		for(int i=0;i<count;i++){
			//天安门的位置
			GeoPoint ret = encryPoint(116.391336,39.907284);
			long dis=GeoUtils.getDistanceInGreatCircle(116.363710,39.899507,116.391336,39.907284);
//			System.out.println("get  point lon:"+ret.getLongitude()+",lat:"+ret.getLatitude()+",distance="+dis);	//32位
			String polyStr = encodeGooglePolyLine(pointArray);
			System.out.println("encode G2511 google ploy:"+polyStr);
		}
		List<GeoPoint> list = new LinkedList<GeoPoint>();
		decodeGooglePolyLine("omoaG}xekVdfCcgBxeBecC~`LexKbb@{i@",list);
		//折线编码效率大约1百万次2秒钟
		LOG.info("run {} times encodeGooglePolyLine method,elapse time={}ms",count,(System.currentTimeMillis()-lstart));
		System.exit(0);
	}
}
